package com.rrd.itext.xmlBean.layoutBean;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.itextpdf.text.Anchor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfTemplate;
import com.rrd.itext.utils.GlobalNames;
import com.rrd.itext.utils.PDFBuilder;
import com.rrd.itext.utils.StyleUtil;
import com.rrd.itext.utils.enumeration.AlignStyle;
import com.rrd.itext.xmlBean.layoutBean.iface.PdfElementRender;
import com.rrd.itext.xmlBean.layoutBean.iface.PositionElement;
import com.rrd.itext.xmlBean.layoutBean.iface.PrintOrder;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;

@XStreamAlias("Paragraph")
public class XmlParagraph extends XmlFontStyle implements PdfElementRender,
		PositionElement {
	@XStreamAsAttribute
	@XStreamAlias("align")
	private String align;
	@XStreamAsAttribute
	@XStreamAlias("postionType")
	private String positionType;
	@XStreamAsAttribute
	@XStreamAlias("offsetX")
	private Float offsetX;
	@XStreamAsAttribute
	@XStreamAlias("offsetY")
	private Float offsetY;
	@XStreamAsAttribute
	@XStreamAlias("width")
	private Integer width;
	@XStreamAsAttribute
	@XStreamAlias("height")
	private Integer height;
	@XStreamAsAttribute
	@XStreamAlias("textAlign")
	private String textAlign;
	@XStreamAsAttribute
	@XStreamAlias("leading")
	private Float leading;

	@XStreamAlias("text")
	private String text;
	@XStreamAlias("List")
	private XmlList list;

	@XStreamAlias("Chunks")
	private List<XmlChunk> chunks;

	private List<XmlHtmlBlock> htmlBlocks;
	// @XStreamAlias("Lists")
	// private List<XmlList> lists;

	private List<PrintOrder> printOrderBean;

	private Comparator<PrintOrder> printOrderComparator;
	private Comparator<XmlChunk> chunkComparator;
	private boolean needResize = false;

	public boolean hasTextContent() {
		return text != null;
	}

	public int getAlign() {
		return AlignStyle.getAlign(this.align);
	}

	public void setAlign(String align) {
		this.align = align;
	}

	public String getPositionType() {
		return positionType;
	}

	public void setPositionType(String positionType) {
		this.positionType = positionType;
	}

	public Float getOffsetX() {
		return offsetX;
	}

	public Float getOffsetY() {
		return offsetY;
	}

	public void setOffsetY(Float offsetY) {
		this.offsetY = offsetY;
	}

	public void setOffsetX(Float offsetX) {
		this.offsetX = offsetX;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	public Integer getWidth() {
		return width;
	}

	public void setWidth(Integer width) {
		this.width = width;
	}

	public Integer getHeight() {
		return height;
	}

	public void setHeight(Integer height) {
		this.height = height;
	}

	public List<XmlChunk> getChunks() {
		return chunks == null ? new ArrayList<XmlChunk>() : chunks;
	}

	public void setChunks(List<XmlChunk> chunks) {
		this.chunks = chunks;
	}

	public void setTextAlign(String textAlign) {
		this.textAlign = textAlign;
	}

	public int getTextAlign() {
		if (textAlign == null)
			return Element.ALIGN_LEFT;
		if (textAlign.equalsIgnoreCase("center"))
			return Element.ALIGN_CENTER;
		if (textAlign.equalsIgnoreCase("justified"))
			return Element.ALIGN_JUSTIFIED;
		if (textAlign.equalsIgnoreCase("left"))
			return Element.ALIGN_LEFT;
		if (textAlign.equalsIgnoreCase("right"))
			return Element.ALIGN_RIGHT;
		return Element.ALIGN_LEFT;
	}

	public XmlList getList() {
		return list;
	}

	public void setList(XmlList l) {
		this.list = l;
	}

	public List<PrintOrder> getPrintOrderBean() {
		printOrderBean = new ArrayList<PrintOrder>();
		if (this.getChunks().size() > 0) {
			printOrderBean.addAll(this.getChunks());
		}
		if (this.getList() != null) {
			printOrderBean.add(this.getList());
		}
		if (this.getHtmlBlocks() != null) {
			printOrderBean.addAll(this.getHtmlBlocks());
		}
		if (printOrderBean.size() > 1) {
			Collections.sort(printOrderBean, getPrintOrderComparator());
		}

		return printOrderBean;
	}

	public void setPrintOrderBean(List<PrintOrder> printOrderBean) {
		this.printOrderBean = printOrderBean;
	}

	public void setPrintOrderComparator(
			Comparator<PrintOrder> printOrderComparator) {
		this.printOrderComparator = printOrderComparator;
	}

	public Comparator<PrintOrder> getPrintOrderComparator() {
		if (printOrderComparator == null) {
			this.printOrderComparator = new Comparator<PrintOrder>() {
				@Override
				public int compare(PrintOrder o1, PrintOrder o2) {
					return o1.getPrintOrder() - o2.getPrintOrder();
				}
			};
		}
		return printOrderComparator;
	}

	public Comparator<XmlChunk> getChunkComparator() {
		if (chunkComparator == null) {
			this.chunkComparator = new Comparator<XmlChunk>() {
				@Override
				public int compare(XmlChunk o1, XmlChunk o2) {
					return o1.getPrintOrder() - o2.getPrintOrder();
				}
			};
		}
		return chunkComparator;
	}

	public void setChunkComparator(Comparator<XmlChunk> chunkComparator) {
		this.chunkComparator = chunkComparator;
	}

	public List<XmlHtmlBlock> getHtmlBlocks() {
		return htmlBlocks;
	}

	public void setHtmlBlocks(List<XmlHtmlBlock> htmlBlocks) {
		this.htmlBlocks = htmlBlocks;
	}

	@Override
	public void render(PdfContentByte cb, PDFBuilder builder) throws Exception {
		Float realLeading = this.getLeading() == null ? GlobalNames.DEFAULT_LEADING : this.getLeading();
		cb.saveState();
		if(this.getWidth() == null){
            this.setWidth(500);
		}
		if (this.getHeight() == null) {
		    needResize = true;
			this.setHeight(10);
		}
		PdfTemplate tp = cb.createTemplate(this.getWidth(), this.getHeight());
		ColumnText ct = new ColumnText(tp);
		ct.setSimpleColumn(tp.getBoundingBox());
		Paragraph para = new Paragraph();
		para.setLeading(realLeading);
		para.setAlignment(this.getTextAlign());
		Paragraph copyPara = (Paragraph) para.clone();
		List<PrintOrder> printOrderBeans = this.getPrintOrderBean();
		for (Object o : printOrderBeans) {
			if (o instanceof XmlChunk) {
				XmlChunk chunk = (XmlChunk) o;
				if (chunk.getNewLine()) {
					
					para.add(Chunk.NEWLINE);
				}
				XmlAnchor anchor = chunk.getAnchor();
				if (anchor != null) {
					setAnchor(para, anchor, chunk, builder);
				} else {
					Chunk ck = chunk.parse(builder);
					if (ck != null) {
						para.add(ck);
					}
				}
			} else if (o instanceof XmlList) {
				XmlList xmlList = (XmlList) o;
				if (xmlList.getLeading() != null) {
					para.setLeading(xmlList.getLeading());
				}
				para.add(xmlList.parse(builder));
			} else if (o instanceof XmlHtmlBlock) {
				XmlHtmlBlock htmlBlock = (XmlHtmlBlock) o;
				if (htmlBlock.isNewLine()) {
					para.add(Chunk.NEWLINE);
				}
				ct.addElement(para);
				para = (Paragraph) copyPara.clone();
				List<Element> htmlElements = (List<Element>) htmlBlock.parse(builder);
				boolean isContainChunk = false;
                for (Element e : htmlElements) {
                    if(e instanceof Chunk){
                        isContainChunk = true;
                        break;
                    }
                }
                //Resolve the span breaking line issue.
                if(isContainChunk){
                    Paragraph p = new Paragraph();
                    p.setLeading(realLeading);
                    for (Element e : htmlElements) {
                        p.add(e);
                    }
                    ct.addElement(p);
                }else{
                    for (Element e : htmlElements) {
                        if (e instanceof Paragraph) {
                        	if(((Paragraph) e).getLeading() == 0f)
                        	{
                        		((Paragraph) e).setLeading(realLeading);
                        	}
                        } else if (e instanceof Phrase) {
                            ((Phrase) e).setLeading(realLeading);
                        }
                        ct.addElement(e);
                    }
                }
			}
		}
		if (!para.isEmpty()) {
			ct.addElement(para);
		}
		if (needResize) {
			boolean flag = ColumnText.hasMoreText(ct.go(false));
			if (flag) {
				cb.restoreState();
		        this.setHeight(this.getHeight() + 10);
		        this.setOffsetY(this.getOffsetY() - 10);
		        this.render(cb, builder);
			}else {
				cb.addTemplate(tp, this.getOffsetX(), this.getOffsetY());
				cb.restoreState();
			}
		} else {
			ColumnText.hasMoreText(ct.go(false));
			cb.addTemplate(tp, this.getOffsetX(), this.getOffsetY());
			cb.restoreState();
		}
	}

	private void setAnchor(Paragraph para, XmlAnchor xmlAnchor,
			XmlChunk xmlChunk, PDFBuilder builder) throws DocumentException,
			IOException {
		if (xmlChunk.getText().getText().contains(xmlAnchor.getTarget())) {
			String[] strArray = xmlChunk.getText().getText().split(xmlAnchor.getTarget());
			int idx = xmlChunk.getText().getText().trim().indexOf(xmlAnchor.getTarget());
			Font anchorFont = StyleUtil.createFont(xmlAnchor, builder.getStyleMap());
			Font chunkFont = StyleUtil.createFont(xmlChunk, builder.getStyleMap());
			Anchor anchor = new Anchor(xmlAnchor.getTarget().replace("%", ""),anchorFont);
			if (idx == 0) {
				para.add(anchor);
				for (String s : strArray) {
					Chunk chunk = new Chunk(s, chunkFont);
					para.add(chunk);
				}
			} else {
				Chunk prefixC = new Chunk(strArray[0], chunkFont);
				para.add(prefixC);
				para.add(anchor);
				if (strArray.length > 1) {
					Chunk surfixC = new Chunk(strArray[1], chunkFont);
					para.add(surfixC);
				}
			}
		}
	}

	public Float getLeading() {
		return leading;
	}

	public void setLeading(Float leading) {
		this.leading = leading;
	}

}
